/*
-------------------------------------------------------------------
BIE is Copyright 2001-2003 Brunswick Corp.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/bie_license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
*/
package com.webdeninteractive.sbie.config;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.log4j.Logger;

import org.apache.commons.digester.Digester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SimpleLog;

import com.webdeninteractive.sbie.config.data.ConfigData;
import com.webdeninteractive.sbie.config.data.Parameter;
import com.webdeninteractive.sbie.config.data.ClientData;
import com.webdeninteractive.sbie.config.data.ServiceData;

/**
 *  Config file parser/editor implementation for XML-based sbie configuration,
 *  implemented using the Jakarta Common's Digester library.
 *
 * @author gfast
 * @version $Id: DigesterConfig.java,v 1.1.1.1 2003/05/20 16:56:49 gdf Exp $ 
 */
public class DigesterConfig implements SystemConfig {

    private Logger logger = Logger.getLogger("com.webdeninteractive.sbie.config.DigesterConfig");

    protected File configFileLocation;
    protected Digester digester;
    protected ConfigData configdata;

    /* Creates the Digester object, and define its parsing rules. */
    public DigesterConfig() {
        digester = new Digester();
        digester.setValidating(false); // don't validate the config file

        /* Uncomment this to debug parsing issues:
        SimpleLog logger = new SimpleLog("XXX");
        logger.setLevel(5);
        digester.setLogger(logger);
        */

        // Global configuration:
        digester.addObjectCreate( "sbie-configuration", 
                                  ConfigData.class );
        digester.addBeanPropertySetter( "sbie-configuration/upgradeDir",
                                        "upgradeDir" );
        digester.addBeanPropertySetter( "sbie-configuration/bootsystem/class",
                                        "bootSystemClassName" );
        digester.addSetProperties( "sbie-configuration/bootsystem", "name",
                                   "bootSystemName" );
        digester.addBeanPropertySetter( "sbie-configuration/extraclass",
                                        "extraClass" );

        // Client elements:
        digester.addObjectCreate( "sbie-configuration/client", 
                                  ClientData.class );
        digester.addSetProperties( "sbie-configuration/client", "name",
                                   "clientName" );
        digester.addBeanPropertySetter( "sbie-configuration/client/class",
                                        "clientClassName" );
        digester.addBeanPropertySetter( "sbie-configuration/client/startup",
                                        "startup" );
        digester.addBeanPropertySetter( 
            "sbie-configuration/client/protocolhandler/class",
            "protocolHandlerClassName" );
        digester.addSetProperties( 
            "sbie-configuration/client/protocolhandler", "name",
            "protocolHandlerName" );
        digester.addSetNext( "sbie-configuration/client", "addClient" );

        // Clients contain service elements:
        digester.addObjectCreate( "sbie-configuration/client/service",
                                  ServiceData.class );
        digester.addSetProperties( "sbie-configuration/client/service", "name",
                                   "serviceName" );
        digester.addBeanPropertySetter( 
            "sbie-configuration/client/service/class", "serviceClassName" );
        digester.addBeanPropertySetter( 
            "sbie-configuration/client/service/interval", "serviceInterval" );
        digester.addSetNext( "sbie-configuration/client/service", 
                             "addService" );

        // Both clients and services may have parameters, so
        // we define a general rule for parameter elements:
        digester.addObjectCreate( "*/parameter", Parameter.class );
        digester.addSetProperties( "*/parameter", "name",
                                   "name" );        
        digester.addBeanPropertySetter( "*/parameter", "value" );
        digester.addSetNext( "*/parameter", "addParameter" );
                                   
    } // DigesterConfig()

    /** Converts this configuration to XML.
     *  (Very fragile).
     */
    public String toFileFormat() {
        return configdata.toString();
    }

    // -----------
    // BootConfig:

    /** Sets the location of this config file.
     *  Setting this causes the file to be loaded and parsed, initializing
     *  rest of this object's data.
     */
    public void setLocation( File configFile )
        throws IOException
    {
        this.configFileLocation = configFile;
        try {
            configdata = (ConfigData)digester.parse( configFile );
            logger.debug( "Read configuration = " + toFileFormat() );
        } catch ( org.xml.sax.SAXException e ) {
            e.printStackTrace(); // XXX
            //throw new IOException("Failed to parse XML: " + e);
        }
    }

    public File getLocation() {
        return this.configFileLocation;
    }

    public File getUpgradeStoreLocation() {
        return configdata.getUpgradeDirAsFile();
    }

    public String getInitialRemoteSystemName() {
        return configdata.getBootSystemName();
    }

    public String getInitialRemoteSystemClassName() {
        return configdata.getBootSystemClassName();
    }

    // -------------
    // SystemConfig: 

    public String[] getStartupClientNames() {
        ArrayList ret = new ArrayList();
        Iterator cit = configdata.getClients().iterator();
        while( cit.hasNext() ) {
            ClientData c = (ClientData)cit.next();
            if ( c.isStartup() ) {
                ret.add(c.getClientName());
            }
        }
        return (String[])ret.toArray( new String[0] );

    } // getStartupClientNames()

    // bootsystem class
    // each: client class
    //     : each: service class
    // (protocolhandlers?)
    public String[] getRequiredClassNames() {
        ArrayList ret = new ArrayList();
        ret.add( configdata.getBootSystemClassName() );
        Iterator cit = configdata.getClients().iterator();
        while( cit.hasNext() ) {
            ClientData c = (ClientData)cit.next();
            ret.add( c.getClientClassName() );
            Iterator sit = c.getServices().iterator();
            while( sit.hasNext() ) {
                ServiceData s = (ServiceData)sit.next();
                ret.add( s.getServiceClassName() );
            }
        }
        Iterator xit = configdata.getExtraClasses().iterator();
        while( xit.hasNext() ) {
            ret.add( (String)xit.next() );
        }
        return (String[])ret.toArray( new String[0] );

    } // getRequiredClassNames()

    public String[] getClientNames() {
        ArrayList ret = new ArrayList();
        Iterator cit = configdata.getClients().iterator();
        while( cit.hasNext() ) {
            ClientData c = (ClientData)cit.next();
            ret.add(c.getClientName());
        }
        return (String[])ret.toArray( new String[0] );

    } // getClientNames()

    /** @return ClientData object for the named client, or null if no such. */
    public ClientData getClientDataByName( String clientName ) {
        Iterator cit = configdata.getClients().iterator();
        while( cit.hasNext() ) {
            ClientData c = (ClientData)cit.next();
            if (c.getClientName().equals(clientName)) {
                return c;
            }
        }
        return null;

    } // getClientDataByName()

    public String getProtocolHandlerName( String clientName ) {
        return getClientDataByName(clientName).getProtocolHandlerName();
    }

    public String getProtocolHandlerClassName( String clientName ) {
        return getClientDataByName(clientName).getProtocolHandlerClassName();
    }

    /** @return Class name implementing the named client, or null if no such.*/
    public String getClientImplClass( String clientName ) {
        ClientData c = getClientDataByName(clientName);
        if (c!=null) {
            return c.getClientClassName();
        } else {
            return null;
        }

    } // getClientImplClass()

    /** @return a Collection of ClientData objects. */        
    public Collection getClientData() {
        return configdata.getClients();
    }

    public String[] getServicesForClient( String clientName ) {
        ArrayList ret = new ArrayList();
        Iterator sit = getServiceData(clientName).iterator();
        while( sit.hasNext() ) {
            ServiceData s = (ServiceData)sit.next();
            ret.add( s.getServiceName() );
        }
        return (String[])ret.toArray( new String[0] );

    } // getServicesForClient() = String[]

    public String getServiceImplClass( String clientName, 
                                       String serviceName ) 
    {
        ServiceData s = getServiceDataByName( clientName, serviceName );
        if (s!=null) {
            return s.getServiceClassName();
        } else {
            return null;
        }
    } // getServiceImplClass()

    public ServiceData getServiceDataByName( String clientName, 
                                             String serviceName )
    {
        Iterator sit = getServiceData(clientName).iterator();
        while( sit.hasNext() ) {
            ServiceData s = (ServiceData)sit.next();
            if ( s.getServiceName().equals(serviceName) ) {
                return s;
            }
        }
        return null;

    } // getServiceDataByName()

    public void setClientData( ClientData newClientData ) {
        // have to go through the list one at a time, and replace the
        // client we're editing here
        ArrayList clientList = new ArrayList();        
        clientList.addAll( configdata.getClients() );
        configdata.clearClients();
        boolean cdIsNew = true; // assume true unless it's found
        Iterator oldclients = clientList.iterator();
        while( oldclients.hasNext() ) {
            ClientData clientdata = (ClientData)oldclients.next();
            if ( newClientData.getClientName().equals( 
                     clientdata.getClientName() ) ) {
                configdata.addClient( newClientData ); // replace old with new
                cdIsNew = false;
            } else {
                configdata.addClient( clientdata );
            }
        }
        if (cdIsNew) {
            configdata.addClient( newClientData );
        }
    } // setClientData()

    public void setServiceData( String clientName,
                                ServiceData serviceData ) {
        ClientData c = getClientDataByName( clientName );
        if (c!=null) {
            // we have to grab the list of services before we clear it:
            ArrayList serviceList = new ArrayList();
            serviceList.addAll( c.getServices() );
            Iterator services = serviceList.iterator();
            // now clear the services, and re-add the needed ones.
            c.clearServices();
            boolean sdIsNew = true; // assume new unless it's in the list
            while( services.hasNext() ) {
                ServiceData s = (ServiceData)services.next();
                if ( s.getServiceName().equals(
                         serviceData.getServiceName() ) ) {
                    c.addService(serviceData); // add the edited one instead
                    sdIsNew = false;
                } else {
                    c.addService(s); // re-add the old one
                }
            }
            if (sdIsNew) {
                c.addService(serviceData);
            }
        } else {
            // It's fatal to try adding a service for a non-existant client
            throw new IllegalArgumentException( "No such client '" + 
                                                clientName + "'" );
        }
    } // setServiceData()


    public Map getServiceParameters( String clientName, String serviceName ) {
        HashMap map = new HashMap();
        // Start with params defined at the client level...
        ClientData c = getClientDataByName(clientName);
        if (c!=null) {
            Iterator paramobj = c.getParameters().iterator();
            while( paramobj.hasNext() ) {
                Parameter p = (Parameter)paramobj.next();
                map.put( p.getName(), p.getValue() );
            }
        }
        // ...then, params defined per service override those.
        ServiceData s = getServiceDataByName(clientName, serviceName);
        if (s!=null) {
            Iterator paramobj = s.getParameters().iterator();
            while( paramobj.hasNext() ) {
                Parameter p = (Parameter)paramobj.next();
                map.put( p.getName(), p.getValue() );
            }
        }
        return map;
        
    } // getServiceParameters()

    /** @return a Collection of ServiceData objects. */
    public Collection getServiceData( String clientName ) {
        ClientData c = getClientDataByName(clientName);
        if ( c!=null ) {
            return c.getServices();
        } else {
            return new ArrayList(0); // return empty collection: no such client
        }
    }

    /** If the ConfigData needs to be re-written by hand. */
    public ConfigData getConfigDataRoot() {
        return configdata;
    }


}
